#ifndef __ZKCLIENT_H__
#define __ZKCLIENT_H__

#include <memory>
#include <functional>
#include <string>
#include <vector>

#ifndef THREADED
#define THREADED
#endif

#include <zookeeper/zookeeper.h>

class ZKClient : public std::enable_shared_from_this<ZKClient>
{
  public:
    struct EventType
    {
        static const int CREATED;     // = ZOO_CREATED_EVENT;
        static const int DELETED;     // = ZOO_DELETED_EVENT;
        static const int CHANGED;     // = ZOO_CHANGED_EVENT;
        static const int CHILD;       // = ZOO_CHILD_EVENT;
        static const int SESSION;     // = ZOO_SESSION_EVENT;
        static const int NOWATCHING;  // = ZOO_NOTWATCHING_EVENT;
    };
    struct FlagType
    {
        static const int EPHEMERAL;  // = ZOO_EPHEMERAL;
        static const int SEQUENCE;   // = ZOO_SEQUENCE;
        static const int CONTAINER;  // = ZOO_CONTAINER;
    };
    struct StateType
    {
        static const int EXPIRED_SESSION;  // = ZOO_EXPIRED_SESSION_STATE;
        static const int AUTH_FAILED;      // = ZOO_AUTH_FAILED_STATE;
        static const int CONNECTING;       // = ZOO_CONNECTING_STATE;
        static const int ASSOCIATING;      // = ZOO_ASSOCIATING_STATE;
        static const int CONNECTED;        // = ZOO_CONNECTED_STATE;
        static const int READONLY;         // = ZOO_READONLY_STATE;
        static const int NOTCONNECTED;     // = ZOO_NOTCONNECTED_STATE;
    };
    using ptr = std::shared_ptr<ZKClient>;
    /**
     * @brief A sharedWathcerFn to ensure that the ZKClient Object will not
     * destruct while the watcher_fn is running
     */
    using SharedWatherFn =
        std::function<void(int, int, const std::string&, ZKClient::ptr)>;
    typedef void (*LogCallback)(const char*);

  public:
    ZKClient();
    ~ZKClient();
    int Init(const std::string& hosts,
             int timeout,
             SharedWatherFn wcb,
             LogCallback lcb = nullptr);
    int SetHosts(const std::string& hosts);
    int Create(const std::string& path,
               const std::string& val,
               std::string& newPath,
               const ACL_vector* acl = &ZOO_OPEN_ACL_UNSAFE,
               int flags = 0);
    int Exists(const std::string& path, bool watch, Stat* stat = nullptr);
    int Delete(const std::string& path, int version = -1);
    int Get(const std::string& path,
            std::string& val,
            bool watch,
            Stat* stat = nullptr);
    int GetConfig(std::string& val, bool watch, Stat* stat = nullptr);
    int Set(const std::string& path,
            const std::string& val,
            int version = -1,
            Stat* stat = nullptr);
    int GetChildren(const std::string& path,
                    std::vector<std::string>& val,
                    bool watch,
                    Stat* stat = nullptr);
    int Close();
    int GetState();
    std::string GetCurrentServer();
    int Reconnect();

  private:
    static void watcherCallback(zhandle_t* zh,
                                int type,
                                int stat,
                                const char* path,
                                void* watcherCtx);
    using WatcherCallback = std::function<void(int, int, const std::string&)>;

  private:
    zhandle_t* handle_{nullptr};
    std::string hosts_{"127.0.0.1:2181"};
    WatcherCallback watcherCallback_;
    LogCallback logCallback_;
    int timeout_{0};
};

#endif  // __ZKCLIENT_H__